%%--------------------------------------------------------------------
%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------

-module(prop_emqx_reason_codes).

-include_lib("emqx/include/emqx_mqtt.hrl").
-include_lib("proper/include/proper.hrl").

%%--------------------------------------------------------------------
%% Properties
%%--------------------------------------------------------------------

prop_name_text() ->
    ?FORALL(
        UnionArgs,
        union_args(),
        is_atom(apply_fun(name, UnionArgs)) andalso
            is_binary(apply_fun(text, UnionArgs))
    ).

prop_compat() ->
    ?FORALL(
        CompatArgs,
        compat_args(),
        begin
            Result = apply_fun(compat, CompatArgs),
            is_number(Result) orelse Result =:= undefined
        end
    ).

prop_connack_error() ->
    ?FORALL(
        CONNACK_ERROR_ARGS,
        connack_error_args(),
        is_integer(apply_fun(connack_error, CONNACK_ERROR_ARGS))
    ).

%%--------------------------------------------------------------------
%% Helper
%%--------------------------------------------------------------------

apply_fun(Fun, Args) ->
    apply(emqx_reason_codes, Fun, Args).

%%--------------------------------------------------------------------
%% Generator
%%--------------------------------------------------------------------

union_args() ->
    frequency([
        {6, [real_mqttv3_rc(), mqttv3_version()]},
        {43, [real_mqttv5_rc(), mqttv5_version()]}
    ]).

compat_args() ->
    frequency([
        {18, [connack, compat_rc()]},
        {2, [suback, compat_rc()]},
        {1, [unsuback, compat_rc()]}
    ]).

connack_error_args() ->
    [
        frequency([
            {10, connack_error()},
            {1, unexpected_connack_error()}
        ])
    ].

connack_error() ->
    oneof([
        client_identifier_not_valid,
        bad_username_or_password,
        bad_clientid_or_password,
        username_or_password_undefined,
        password_error,
        not_authorized,
        server_unavailable,
        server_busy,
        banned,
        bad_authentication_method
    ]).

unexpected_connack_error() ->
    oneof([who_knows]).

real_mqttv3_rc() ->
    frequency([
        {6, mqttv3_rc()},
        {1, unexpected_rc()}
    ]).

real_mqttv5_rc() ->
    frequency([
        {43, mqttv5_rc()},
        {2, unexpected_rc()}
    ]).

compat_rc() ->
    frequency([
        {95, ?SUCHTHAT(RC, mqttv5_rc(), RC >= 16#80 orelse RC =< 2)},
        {5, unexpected_rc()}
    ]).

mqttv3_rc() ->
    oneof(mqttv3_rcs()).

mqttv5_rc() ->
    oneof(mqttv5_rcs()).

unexpected_rc() ->
    oneof(unexpected_rcs()).

mqttv3_rcs() ->
    [0, 1, 2, 3, 4, 5].

mqttv5_rcs() ->
    [
        16#00,
        16#01,
        16#02,
        16#04,
        16#10,
        16#11,
        16#18,
        16#19,
        16#80,
        16#81,
        16#82,
        16#83,
        16#84,
        16#85,
        16#86,
        16#87,
        16#88,
        16#89,
        16#8A,
        16#8B,
        16#8C,
        16#8D,
        16#8E,
        16#8F,
        16#90,
        16#91,
        16#92,
        16#93,
        16#94,
        16#95,
        16#96,
        16#97,
        16#98,
        16#99,
        16#9A,
        16#9B,
        16#9C,
        16#9D,
        16#9E,
        16#9F,
        16#A0,
        16#A1,
        16#A2
    ].

unexpected_rcs() ->
    ReasonCodes = mqttv3_rcs() ++ mqttv5_rcs(),
    Unexpected = lists:seq(0, 16#FF) -- ReasonCodes,
    lists:sublist(Unexpected, 5).

mqttv5_version() ->
    ?MQTT_PROTO_V5.

mqttv3_version() ->
    oneof([?MQTT_PROTO_V3, ?MQTT_PROTO_V4]).
